Option Explicit

'################################################################
' Discriptor
' 字符串分割提取器
'################################################################

' 匹配字符串类型: 可忽略分隔符
Private Const IGNORABLE_DELIMITER As Integer = 1

' 匹配字符串类型: 不可忽略分隔符
Private Const RETAINABLE_DELIMITER As Integer = 2

' 匹配字符串类型: 包围字符串对 key1
Private Const SURROUND_PAIR_KEY1 As Integer = 3

'################################################################
' Field
'################################################################
'---------------------------------------
' 分割字符集合
'---------------------------------------
' 可忽略的分割字符串集合 - 不需要保留的分割字符串
' Map<String, IStringNextTable>
Private ignorableDelimiterMap As Object

' 不可忽略的分割字符串集合 - 需要保留的分割字符串
' Map<String, IStringNextTable>
Private retainableDelimiterMap As Object

' 不做分割的包围字符串对。这个字符串对中间的字符串(包含两端的字符串对)不会执行字符串分割
' 如单引号、双引等
' 两端的包围字符串不应该删除
'
' 保存包围字符对中的 key1, 及其对应的多个 key2
' Map<String, IObjArrayRef<IStringNextTable>>
Private surroundPairMap As Object

' 保存: 包围字符对中的 key1
' Map<String, IStringNextTable>
Private surroundPairKey1Map As Object

'---------------------------------------
' 原始字符串缓存
'---------------------------------------
' 被分割的原始字符串
Private originStr As String

' 原始字符串的长度
Private originStrLen As Long

'---------------------------------------
' 匹配缓存
'---------------------------------------
' 分割字符串 + 包围字符串对key1 的集合，并按照字符串的长度升序排序
' 该缓存一定会存在
' IObjArrayRef<IStringNextTable>
Private compareStrArray As IObjArrayRef

' compareStrTypeMap 缓存内各个字符串的类型
' Map<String, Integer>
' 该缓存一定会存在
'
' 使用以下常量来保存
'  可忽略分隔符      : IGNORABLE_DELIMITER
'  不可忽略分隔符    : RETAINABLE_DELIMITER
'  包围字符串对 key1 : SURROUND_PAIR_KEY1
Private compareStrTypeMap As Object

' 保存 compareStrArray 中存在的 startwith 关系
' Map<String, IObjArrayRef<IStringNextTable>>
' 每个 key 下的 ArrayRef 都是有序的
'
' 如果 compareStrArray 中完全不存在 startwith 关系
' 则该缓存为 Nothing
Private compareStrStartwithMap As Object

' 保存 compareStrArray 中存在的 endwith 关系
' Map<String, IObjArrayRef<IStringNextTable>>
' 每个 key 下的 ArrayRef 都是有序的
'
' 如果 compareStrArray 中完全不存在 endwith 关系
' 则该缓存为 Nothing
Private compareStrEndwithMap As Object

' 缓存包围字符串对中: 各 key1 下的 key2 之间的 startwith 关系，并生成缓存
' Map<Key1Str, Map<Key2String, IObjArrayRef<IStringNextTable>>>
'
' 如果所有 key1 下的多个 key2 之间没有这种关系
' 则该缓存为 Nothing
Private surroundKey2StartWithMap As Object

'---------------------------------------
' 分割后的字符串缓存
'---------------------------------------

' 当前正在处理的字符串索引位置
' 字符串索引从 1 开始
' 如果当前属性 > originStrLen 时，则当前字符串中 的所有字符全部处理完成
Private originStrActiveIndex As Long

' 下一次截取字符串时的起始索引
Private nextTokenStartIndex As Long

' 下一次调用 nextToken 时需要返回字符串
Private nextToken As String

' 对于【不可忽略分隔符】 + 搜索到的包围字符串
' 如果在找到这类字符串时，从其前面有分割出了一份字符串
' 则这类字符串只能缓存在 noConsumeStr 中，并在第二次调用 nextToken 时返回
Private noConsumeStr As String

'---------------------------------------
' 其他缓存
'---------------------------------------
' 数据放回栈
Private putBackStack As PrimitiveStack

' 当前活动索引对应的字符
Private activeChar As String

'#######################################
' Constructor
'#######################################
Private Sub Class_Initialize()
    Set putBackStack = New PrimitiveStack
End Sub

Private Sub Class_Terminate()

    ' 清除: 分割字符集合
    Set ignorableDelimiterMap = Nothing
    Set retainableDelimiterMap = Nothing
    Set surroundPairMap = Nothing
    Set surroundPairKey1Map = Nothing

    ' 清除: 匹配缓存
    Set compareStrArray = Nothing
    Set compareStrTypeMap = Nothing
    Set compareStrStartwithMap = Nothing
    Set surroundKey2StartWithMap = Nothing

    ' 清除: 其他缓存
    Set putBackStack = Nothing

End Sub

'################################################################
' Config 配置方法
'################################################################
' 添加可忽略的分隔符字符串
' @param s, 可忽略的分隔符字符串
' @param ignoreCase, 是否需要忽略大小写。默认 = False
Public Sub addIgnorableDelimiter(ByRef s As String, Optional ByRef ignoreCase As Boolean = False)
    ' 1. 检查参数
    ' 1.1 参数不能是空字符串
    If s = "" Then
        Err.Raise 10007, Description:="String parameter[s] must not be empty string."
    End If

    ' 1.2 当前字符串不能在【不可忽略的分隔符字符串】中存在
    If Not retainableDelimiterMap Is Nothing Then
        If retainableDelimiterMap.Exists(s) Then
            Err.Raise 60000, Description:="parameter s=" & s & _
                " has been saved in retainable delimiter, can not save as a ignorable delimiter"
            Exit Sub
        End If
    End If

    ' 1.3 当前字符串不能在【包围字符对】的 key1 集合中
    If Not surroundPairKey1Map Is Nothing Then
        If surroundPairKey1Map.Exists(s) Then
            Err.Raise 60001, Description:="parameter s=" & s & _
                " has been saved in surroundPair's start string(key1), can not save as a ignorable delimiter"
            Exit Sub
        End If
    End If

    If ignorableDelimiterMap Is Nothing Then
        ' 2. 保证缓存已经创建
        Set ignorableDelimiterMap = UtilsCreateSysObj.createMap
    ElseIf ignorableDelimiterMap.Exists(s) = True Then
        ' 3. 如果已经存在，则忽略
        Exit Sub
    End If

    ' 4. 创建前缀表并保存
    Dim nt As IStringNextTable
    Set nt = createNextTable(s, ignoreCase)
    ignorableDelimiterMap.Add nt.OriginStr, nt

    ' 5. 清空匹配缓存
    clearCache
End Sub

' 添加不可忽略的分隔符字符串
' @param s, 不可忽略的分隔符字符串
' @param ignoreCase, 是否需要忽略大小写。默认 = False
Public Sub addRetainableDelimiter(ByRef s As String, Optional ByRef ignoreCase As Boolean = False)
    ' 1. 检查参数
    ' 1.1 参数不能是空字符串
    If s = "" Then
        Err.Raise 10007, Description:="String parameter[s] must not be empty string."
    End If

    ' 1.2 当前字符串不能在【可忽略的分隔符字符串】中存在
    If Not ignorableDelimiterMap Is Nothing Then
        If ignorableDelimiterMap.Exists(s) Then
            Err.Raise 60002, Description:="parameter s=" & s & _
                " has been saved in ignorable delimiter, can not save as a retainable delimiter"
            Exit Sub
        End If
    End If

    ' 1.3 当前字符串不能在【包围字符对】的 key1 集合中
    If Not surroundPairKey1Map Is Nothing Then
        If surroundPairKey1Map.Exists(s) Then
            Err.Raise 60003, Description:="parameter s=" & s & _
                " has been saved in surroundPair's start string(key1), can not save as a retainable delimiter"
            Exit Sub
        End If
    End If

    If retainableDelimiterMap Is Nothing Then
        ' 2. 保证缓存已经创建
        Set retainableDelimiterMap = UtilsCreateSysObj.createMap
    ElseIf retainableDelimiterMap.Exists(s) = True Then
        ' 3. 如果已经存在，则忽略
        Exit Sub
    End If

    ' 4. 创建前缀表并保存
    Dim nt As IStringNextTable
    Set nt = createNextTable(s, ignoreCase)
    retainableDelimiterMap.Add nt.OriginStr, nt

    ' 5. 清空匹配缓存
    clearCache
End Sub

' 添加不做分割的包围字符串对
' @param k1, 包围字符串对的起始字符串（最终的分割结果中会包含起始字符串）
' @param k2, 包围字符串对的结束字符串（最终的分割结果中会包含结束字符串）
' @param ignoreCaseK1, k1 在比较时是否需要忽略大小写。默认 = False
' @param ignoreCaseK2, k2 在比较时是否需要忽略大小写。默认 = False
Public Sub addSurroundPair( _
    ByRef k1 As String, ByRef k2 As String, Optional ByRef ignoreCaseK1 As Boolean = False, _
    Optional ByRef ignoreCaseK2 As Boolean = False)

    ' 1. 检查参数
    ' 1.1 两个参数都不能是空字符串
    If k1 = "" Then
        Err.Raise 10007, Description:="String parameter[k1] must not be empty string."
    End If
    If k2 = "" Then
        Err.Raise 10007, Description:="String parameter[k2] must not be empty string."
    End If

    ' 1.2 当前字符串不能在【可忽略的分隔符字符串】中存在
    If Not ignorableDelimiterMap Is Nothing Then
        If ignorableDelimiterMap.Exists(k1) Then
            Err.Raise 60004, Description:="parameter k1=" & k1 & _
                " has been saved in surroundPair's start string(key1), can not save as a ignorable delimiter"
            Exit Sub
        End If
    End If

    ' 1.3 当前字符串不能在【包围字符对】的 key1 集合中
    If Not retainableDelimiterMap Is Nothing Then
        If retainableDelimiterMap.Exists(k1) Then
            Err.Raise 60005, Description:="parameter k1=" & k1 & _
                " has been saved in surroundPair's start string(key1), can not save as a retainable delimiter"
            Exit Sub
        End If
    End If

    ' 2. 保证缓存已经创建
    If surroundPairKey1Map Is Nothing Then
        Set surroundPairKey1Map = UtilsCreateSysObj.createMap
        Set surroundPairMap = UtilsCreateSysObj.createMap
    End If

    Dim k2NextTable As IStringNextTable
    Dim key2ArrayRef As IObjArrayRef
    If Not surroundPairKey1Map.Exists(k1) Then
        ' 3. 如果 key1 不存在，则直接保存
        ' 保存 key1
        Dim k1nt As IStringNextTable
        Set k1nt = createNextTable(k1, ignoreCaseK1)
        surroundPairKey1Map.Add k1nt.OriginStr, k1nt

        ' 创建 key2
        Set key2ArrayRef = UtilsArrayRef.createRealObjArrayRef(2)
        key2ArrayRef.Add createNextTable(k2, ignoreCaseK2)

        ' 保存 key1 + key2 的关系
        surroundPairMap.Add k1, key2ArrayRef
    Else
        ' 4. 如果 key1 已经存在，则需要首先遍历整个 key2 数组以保证 key1 下不存在重复的 key2
        Set key2ArrayRef = surroundPairMap.Item(k1)

        Dim i As Long
        For i = 0 To key2ArrayRef.MaxIndex
            Set k2NextTable = key2ArrayRef.Item(i)

            If k2NextTable.OriginStr = k2 Then
                ' 如果 k2 重复了，则不需要做任何处理，立刻结束
                Exit Sub
            End If
        Next

        ' 到这里说明 key2 没有重复，需要创建前缀表并缓存
        key2ArrayRef.Add createNextTable(k2, ignoreCaseK2)
    End If

    ' 5. 清空匹配缓存
    clearCache
End Sub

' 批量添加多个可忽略分割符
' @param strArr, 可忽略的分隔符字符串数组
' @param ignoreCase, 是否需要忽略大小写。默认 = False
Public Sub addMultiIgnorableDelimiter( _
    ByRef strArr As Variant, _
    Optional ByRef ignoreCase As Boolean = False)

    ' 1. 检查参数
    If Not IsArray(strArr) Then
        Err.Raise 10005, Description:="parameter strArr val must be Array"
        Exit Sub
    End If

    ' 2. 遍历所有分割符并添加
    Dim i As Integer
    For i = LBound(strArr) To UBound(strArr)
        addIgnorableDelimiter strArr(i) & "", ignoreCase
    Next
End Sub

' 批量添加多个不可忽略分割符
' @param strArr, 不可忽略的分隔符字符串数组
' @param ignoreCase, 是否需要忽略大小写。默认 = False
Public Sub addMultiRetainableDelimiter( _
    ByRef strArr As Variant, _
    Optional ByRef ignoreCase As Boolean = False)

    ' 1. 检查参数
    If Not IsArray(strArr) Then
        Err.Raise 10005, Description:="parameter strArr val must be Array"
        Exit Sub
    End If

    ' 2. 遍历所有分割符并添加
    Dim i As Integer
    For i = LBound(strArr) To UBound(strArr)
        addRetainableDelimiter strArr(i) & "", ignoreCase
    Next
End Sub

' 创建前缀表
' @param s 字符串
' @param ignoreCase 是否需要忽略大小写
Private Function createNextTable(ByRef s As String, ByRef ignoreCase As Boolean) As IStringNextTable
    If ignoreCase Then
        Dim icNextTable As IgnoreCaseStrNextTable
        Set icNextTable = New IgnoreCaseStrNextTable
        icNextTable.Init s

        Set createNextTable = icNextTable

    Else
        Dim simpleNextTable As SimpleStrNextTable
        Set simpleNextTable = New SimpleStrNextTable
        simpleNextTable.Init s
        Set createNextTable = simpleNextTable

    End If
End Function

'################################################################
' Function
'################################################################
' 启动字符串解析，配置相关的缓存
Public Sub analyze(ByRef s As String)
    ' 检查当前是否已经存在缓存了
    Dim hasCache As Boolean
    hasCache = (Not compareStrArray Is Nothing)

On Error GoTo catch

    ' 1. 保存原始字符串的相关信息
    originStr = s
    originStrLen = Len(s)

    ' 2. 检查是否需要重新创建缓存
    '    如果相关缓存已经创建，说明至少已经执行过一次 analyze
    '    并且之后没有再添加过任何分割符 或者 包围字符串对，所以不需要再重新创建匹配缓存
    If Not hasCache Then
        ' 3. 合并所有字符集并排序，然后保存成数组引用
        createCompareStrArray

        ' 4. 收集 compareStrArray 中的所有字符串的分割类型
        collectCompareStrArrayType

        ' 5. 从 compareStrArray 收集字符串之间的关系集合
        createRelationStrMapFromCompareStrArray

        ' 6. 处理包围字符对中的 key2
        ' 6.1 对包含了多个 key2 的情况进行排序
        sortSurroundKey2
        ' 6.2 收集各 key1 下的 key2 之间的 startwith 关系，并生成缓存
        createStartwithMapFromSurroundKey2
    End If

    ' 7. 设置活动索引 + 分割字符串缓存
    originStrActiveIndex = 1
    nextTokenStartIndex = 1
    nextToken = ""
    noConsumeStr = ""

    ' 8. 清空放回缓存
    putBackStack.clear

    Exit Sub
catch:
    ' 解析发生异常
    ' 如果没有缓存，并且在创建缓存时发生了异常，需要手动情况异常，以防止 Debug 等情况下的问题
    If Not hasCache Then
        clearCache
    End If

    ' 抛出原始异常
    Err.Raise Err.Number, Description:=Err.Description
End Sub

' 检查被分割的字符串中，是否还存在没有抽取的字符串
Public Function hasToken() As Boolean
    ' 1. 检查缓存中是否存在可用数据
    ' 1.1 putBack 栈中是否存在数据
    ' 1.2 缓存中是否存在未消费的数据
    ' 1.2.1 因为存在【不可忽略的分割符】，在第 N 次完成字符串分割后，这个【不可忽略的分割符】并没有作为结果返回
    '    所以在第 N + 1 次尝试获取一个字符串时，应该返回这个【不可忽略的分割符】
    ' 1.2.2 因为存在【包围字符串对】，和【不可忽略的分割符】类似，搜索到的包围字符串只能在下一次调用时返回

    ' 1.1
    If putBackStack.Count > 0 Then
        hasToken = True
        Exit Function
    End If

    ' 1.2
    If noConsumeStr <> "" Then
        ' 移动缓存
        nextToken = noConsumeStr
        noConsumeStr = ""

        hasToken = True
        Exit Function
    End If

    ' 2. 遍历原始字符串中的每个字符串，并尝试搜索分割字符串
    Dim matchedStr As IStringNextTable ' 匹配成功的分割符 或 包围字符串对的 key1
    Dim i As Long
    Dim elem As IStringNextTable
    Dim tempStr As IStringNextTable
    Dim hasExtractedStr As Boolean ' 分割字符串后是否成功的抽取到了字符串
    While hasChar
        ' 3. 移动到下一个活动字符
        toNextActiveChar

        ' 4. 遍历所有的比较字符串的前缀表，搜索能够匹配的字符串
        Set matchedStr = Nothing
        For i = 0 To compareStrArray.MaxIndex
            Set elem = compareStrArray.Item(i)

            ' 自动匹配当前字符
            ' 如果发现某个数据已经匹配成功了，则立刻结束遍历
            elem.matchChar activeChar
            if elem.isWholeMatched Then
                Set matchedStr = elem
                Exit For
            end if
        next

        ' 5. 如果发现了一个匹配成功的字符串，则
        '    继续检查 startwith + endwth 关系
        If Not matchedStr Is Nothing Then
            ' 如果能够搜索到新的匹配字符串，则保存到 matchedStr
            Set tempStr = matchInRelationStrMap(matchedStr)
            If Not tempStr Is Nothing Then
                Set matchedStr = tempStr
            End If
        End if

        ' 6. 如果存在匹配成功的字符串，则按照其类型进行处理
        If Not matchedStr Is Nothing Then
            ' 6.1 对字符串进行分割
            hasExtractedStr = splitAndExtractFromOriginStr(matchedStr)
            ' 6.2 遍历 compareStrArray 中的所有字符串，将每个字符串的活动索引设置为 0
            resetCompareStrArray

            ' 6.3 按照分割结果计算检查是否需要结束
            '    如果返回值是空字符串，说明这次没有能够找到可用的 token，需要继续遍历并搜索
            '    如果返回的不是空字符串，则说明找到了一个可以返回的 token，则立刻结束，返回 true
            If hasExtractedStr Then
                hasToken = True
                Exit Function
            End If
        End if
    Wend

    ' 7. 收尾处理
    '    如果进入了这里，只有两种情况
    ' 7.1 在末尾部分全部都是可忽略分割符，虽然原始字符串已经遍历结束了，但是已经没有任何可用的token了，需要
    '    返回 false
    '    如
    '       1233
    '         3
    '          3
    ' 7.2 从 nextTokenStartIndex 开始到末尾部分全部，都是非分割字符串，这种情况应该缓存这部分字符串作为 token
    '    并返回 true
    ' 收尾处理，将最后未处理的字符进行分类
    ' （因为 nextTokenStartIndex 从 1 开始，所以这里是 <=, 发生 = 的时候，说明还剩余一个字符）
    If nextTokenStartIndex <= originStrLen Then
        ' 将上一次的截取位置到原始字符串末尾的这部分字符串作为分割结果返回
        nextToken = Mid(originStr, nextTokenStartIndex)
        ' 需要在这里手动将活动索引推进到末尾的后面 !!!!!
        nextTokenStartIndex = originStrLen + 1
        hasToken = True
    Else
        hasToken = False
    End If
End Function

' 抽取一个切割出来的字符串
Public Function getNextToken() As String
    ' 1. 如果放回缓存中存在数据，则优先返回
    If putBackStack.Count > 0 Then
        getNextToken = putBackStack.pop
        Exit Function
    End If

    getNextToken = nextToken
End Function

' 将抽取的字符串再放回提取器中
' @param v 需要放回的数据
Public Sub putBack(ByRef v As String)
    If putBackStack Is Nothing Then
        Set putBackStack = New PrimitiveStack
    End If

    putBackStack.push v
End Sub

'################################################################
' Private Function
'################################################################
'---------------------------------------
' 清除缓存
'---------------------------------------
Private Sub clearCache()
    If compareStrArray Is Nothing Then
        Exit Sub
    End If

    ' 清除: 匹配缓存
    Set compareStrArray = Nothing
    Set compareStrTypeMap = Nothing
    Set compareStrStartwithMap = Nothing
    Set surroundKey2StartWithMap = Nothing
End Sub

'---------------------------------------
' 解析阶段处理 -- 创建缓存
'---------------------------------------
' 创建分割符 + 包围字符串对key1 的缓存
Private Sub createCompareStrArray()
    ' 1. 计算缓存的长度
    Dim cacheSize As Long
    cacheSize = 0
    ' 收集 可忽略的分割字符串集合 的数量
    If Not ignorableDelimiterMap Is Nothing Then
        cacheSize = cacheSize + ignorableDelimiterMap.Count
    End If

    ' 收集 不可忽略的分割字符串集合 的数量
    If Not retainableDelimiterMap Is Nothing Then
        cacheSize = cacheSize + retainableDelimiterMap.Count
    End If

    ' 收集 包围字符串对key1 的数量
    If Not surroundPairKey1Map Is Nothing Then
        cacheSize = cacheSize + surroundPairKey1Map.Count
    End If

    ' 如果字符串的数量为 0，需要抛出异常
    If cacheSize = 0 Then
        Err.Raise 60006, Description:="Can not analyze string, because not config Delimiter or surroundPair<Key1, Key2>"
        Exit Sub
    End If

    ' 2. 创建缓存，并保存字符
    Set compareStrArray = UtilsArrayRef.createRealObjArrayRef(cacheSize)

    ' 保存: 可忽略的分割字符串集合
    Dim elem As Variant
    If Not ignorableDelimiterMap Is Nothing Then
        For Each elem In ignorableDelimiterMap.Items
            compareStrArray.Add elem
        Next
    End If

    ' 保存: 不可忽略的分割字符串集合
    If Not retainableDelimiterMap Is Nothing Then
        For Each elem In retainableDelimiterMap.Items
            compareStrArray.Add elem
        Next
    End If

    ' 保存: 包围字符串对key1
    If Not surroundPairKey1Map Is Nothing Then
        For Each elem In surroundPairKey1Map.Items
            compareStrArray.Add elem
        Next
    End If

    ' 3. 按照字符串长度，对缓存进行升序排序
    fastSortCompareStrArray 0, cacheSize - 1
End Sub

' 对 compareStrArray 按照字符串长度进行快排
Private Sub fastSortCompareStrArray(ByRef left As Long, ByRef right As Long)
    Dim l As Long
    Dim r As Long
    l = left
    r = right

    Dim pivot As Integer
    Dim rbackup As IStringNextTable
    Set rbackup = compareStrArray.Item(r)
    pivot = rbackup.Length

    While l < r
        ' 保证左侧最小
        While compareStrArray.Item(l).Length <= pivot And l < r
           l = l + 1
        Wend
        compareStrArray.setVal r, compareStrArray.Item(l)

        ' 保证右侧最大
        While compareStrArray.Item(r).Length >= pivot And l < r
           r = r - 1
        Wend
        compareStrArray.setVal l, compareStrArray.Item(r)
    Wend

    compareStrArray.setVal r, rbackup

    If left < (r - 1) Then
        fastSortCompareStrArray left, r - 1
    End If

    If right > r + 1 Then
        fastSortCompareStrArray r + 1, right
    End If
End Sub

' 从 compareStrArray 收集字符串之间的关系集合
' 从 compareStrArray 收集 startwith + endwith 关系
Public Sub createRelationStrMapFromCompareStrArray()
    ' 1. 记录 compareStrArray 中的最大长度
    Dim compareStrMaxLength As Integer
    compareStrMaxLength = compareStrArray.Item(compareStrArray.MaxIndex).Length

    ' 2. 收集 compareStrArray 中各个长度的字符串的结束索引
    Dim lenEndIndexMap As Object
    Set lenEndIndexMap = UtilsCreateSysObj.createMap

    Dim i As Long
    Dim curLen As Integer
    curLen = compareStrArray.Item(0).Length
    Dim temp As IStringNextTable
    For i = 1 To compareStrArray.MaxIndex
        Set temp = compareStrArray.Item(i)
        If temp.Length > curLen Then
            ' 如果长度发生了变化，则说明前一个位置是 curLen 这个长度的字符串的最后一位
            ' 缓存位置
            lenEndIndexMap.Add curLen, i - 1
            curLen = temp.Length
        End If
    Next

    ' 再添加最后一个字符串(最长的字符串)的长度，以防止只有一种长度的字符串时的异常
    If Not lenEndIndexMap.Exists(compareStrMaxLength) Then
        lenEndIndexMap.Add compareStrMaxLength, compareStrArray.MaxIndex
    End If

    ' 3. 收集 startwith 关系
    Dim j as Long
    Dim checkStr As IStringNextTable
    Dim collectArray As IObjArrayRef
    Dim endwithCollectArray As IObjArrayRef
    For i = 0 To compareStrArray.MaxIndex
        Set temp = compareStrArray.Item(i)

        ' 如果当前字符串的长度已经到达了 compareStrArray 中的最大长度
        ' 后续就不会再产生 startwith 关系了，立刻结束遍历
        If temp.Length = compareStrMaxLength Then
            Exit For
        End If

        ' 获取当前长度的最后一位
        j = lenEndIndexMap.Item(temp.Length)

        ' 从下一个长度开始搜索 temp 的 startwith 关系
        For j = j + 1 To compareStrArray.MaxIndex
            Set checkStr = compareStrArray.Item(j)
            If checkStr.startwith(temp) Then
                ' 确保缓存已创建
                If compareStrStartwithMap Is Nothing Then
                    Set compareStrStartwithMap = UtilsCreateSysObj.createMap
                End If

                ' 如果是第一次发现一个以 temp 开头的字符串，则创建收集数组，并缓存
                If collectArray Is Nothing Then
                    Set collectArray = UtilsArrayRef.createRealObjArrayRef(2)

                    compareStrStartwithMap.Add temp.OriginStr, collectArray
                End If

                ' 保存当前找到的这个字符串
                collectArray.Add checkStr
            ElseIf checkStr.endwith(temp) Then
                ' 确保缓存已创建
                If compareStrEndwithMap Is Nothing Then
                    Set compareStrEndwithMap = UtilsCreateSysObj.createMap
                End If

                ' 如果是第一次发现一个以 temp 结尾的字符串，则创建收集数组，并缓存
                If endwithCollectArray Is Nothing Then
                    Set endwithCollectArray = UtilsArrayRef.createRealObjArrayRef(2)

                    compareStrEndwithMap.Add temp.OriginStr, endwithCollectArray
                End If

                ' 保存当前找到的这个字符串
                endwithCollectArray.Add checkStr
            End If
        Next

        ' 每次完成一个字符的检查后，清空startwith关系收集数组
        Set collectArray = Nothing
        Set endwithCollectArray = Nothing
    Next

    ' 4. 将 compareStrStartwithMap 下各 value 中的字符串从缓存 compareStrArray 中删除
    If Not compareStrStartwithMap Is Nothing Then
        ' 4.1 收集所有 value
        Dim valSet As ISet
        Set valSet = UtilsSet.createHashSet
        Dim elem As Variant

        ' 4.2 收集 startwith 关系中的所有字符串
        For Each elem In compareStrStartwithMap.Items
            Set collectArray = elem
            For i = 0 To collectArray.MaxIndex
                valSet.Add collectArray.Item(i)
            Next
        Next

        ' 4.3 从 4.2 的收集结果中删除 endwith 关系中的所有字符串
        If Not compareStrEndwithMap Is Nothing Then
            For Each elem In compareStrEndwithMap.Items
                Set collectArray = elem
                For i = 0 To collectArray.MaxIndex
                    Set checkStr = collectArray.Item(i)

                    If valSet.Exists(checkStr) Then
                        valSet.Remove checkStr
                    End If
                Next
            Next
        End If

        ' 4.4 将 valueSet 中的索引对象从缓存 compareStrArray 中删除
        For i = 0 To (valSet.Count - 1)
            compareStrArray.removeVal valSet.Item(i)
        Next
    End If
End Sub

' 对包围字符串对中: 包含了多个 key2 的情况进行排序
Private Sub sortSurroundKey2()
    ' 1. 如果没有设置包围字符串对，则立刻停止
    If surroundPairMap Is Nothing Then
        Exit Sub
    End If

    ' 2. 遍历所有的 key2 集合，并进行排序
    Dim temp As Variant
    Dim arr As IObjArrayRef
    For Each temp In surroundPairMap.Items
        Set arr = temp
        ' 如果当前集合只有一个字符串，则不进行排序
        If arr.MaxIndex > 0 Then
            fastSortStringNextTableArray arr, 0, arr.MaxIndex
        End If
    Next
End Sub

' 为 IObjArrayRef<IStringNextTable> 集合按照字符串的长度进行升序排列
Private Sub fastSortStringNextTableArray(ByRef arr As IObjArrayRef, ByRef left As Long, ByRef right As Long)
    Dim l As Long
    Dim r As Long
    l = left
    r = right

    Dim rbackup As IStringNextTable
    Dim pivot As Integer
    Set rbackup = arr.Item(r)
    pivot = rbackup.Length

    While l < r
        While arr.Item(l).Length <= pivot And l < r
            l = l + 1
        Wend
        arr.setVal r, arr.Item(l)

        While arr.Item(r).Length >= pivot And l < r
            r = r - 1
        Wend
        arr.setVal l, arr.Item(r)
    Wend

    arr.setVal r, rbackup

    If left < (r - 1) Then
        fastSortStringNextTableArray arr, left, r - 1
    End If

    If right > (r + 1) Then
        fastSortStringNextTableArray arr, r + 1, right
    End If
End Sub

' 收集各 key1 下的 key2 之间的 startwith 关系，并生成缓存
Private Sub createStartwithMapFromSurroundKey2()
    ' 1. 如果没有设置包围字符串对，则立刻停止
    If surroundPairMap Is Nothing Then
        Exit Sub
    End If

    ' 2. 遍历所有的 key2 集合，并进行排序
    Dim key2StartWithRelationMap As Object
    Dim collectArray As IObjArrayRef
    Dim key2Array As IObjArrayRef
    Dim temp As IStringNextTable
    Dim checkStr As IStringNextTable
    Dim i As Long
    Dim j As Long
    Dim key1 As Variant
    For Each key1 In surroundPairMap.Keys
        Set key2Array = surroundPairMap.Item(key1)

        ' 如果当前集合只有一个字符串，则不需要收集
        If key2Array.MaxIndex <= 0 Then
            Goto continue1
        End If

        For i = 0 To key2Array.MaxIndex
            Set temp = key2Array.Item(i)

            For j = j + 1 To key2Array.MaxIndex
                Set checkStr = key2Array.Item(j)

                If checkStr.startwith(temp) Then
                    ' 确保缓存已创建
                    If surroundKey2StartWithMap Is Nothing Then
                        Set surroundKey2StartWithMap = UtilsCreateSysObj.createMap
                    End If

                    ' 如果是在当前 key1 下第一次发现一个 startwith 关系，则创建 map 并缓存
                    If key2StartWithRelationMap Is Nothing Then
                        Set key2StartWithRelationMap = UtilsCreateSysObj.createMap
                        surroundKey2StartWithMap.Add key1, key2StartWithRelationMap
                    End If

                    ' 如果是第一次发现一个以 temp 开头的字符串，则创建收集数组，并缓存
                    If collectArray Is Nothing Then
                        Set collectArray = UtilsArrayRef.createRealObjArrayRef(2)

                        key2StartWithRelationMap.Add temp.OriginStr, collectArray
                    End If

                    collectArray.Add checkStr
                End If
            Next

            ' 每次完成一个字符的检查后，清空startwith关系收集数组
            Set collectArray = Nothing
        Next

        ' 每次完成一个 key1 的检查后，清空startwith关系收集 map
        Set key2StartWithRelationMap = Nothing

continue1:
    Next
End Sub

' 收集 compareStrArray 中的所有字符串的分割类型
Private Sub collectCompareStrArrayType()
    Set compareStrTypeMap = UtilsCreateSysObj.createMap

    Dim i As Long
    Dim temp As IStringNextTable
    For i = 0 To compareStrArray.MaxIndex
        Set temp = compareStrArray.Item(i)

        If Not ignorableDelimiterMap Is Nothing Then
            If ignorableDelimiterMap.Exists(temp.OriginStr) Then
                compareStrTypeMap.Add temp.OriginStr, IGNORABLE_DELIMITER

                Goto continue1
            End If
        End If

        If Not retainableDelimiterMap Is Nothing Then
            If retainableDelimiterMap.Exists(temp.OriginStr) Then
                compareStrTypeMap.Add temp.OriginStr, RETAINABLE_DELIMITER

                Goto continue1
            End If
        End If

        If Not surroundPairKey1Map Is Nothing Then
            If surroundPairKey1Map.Exists(temp.OriginStr) Then
                compareStrTypeMap.Add temp.OriginStr, SURROUND_PAIR_KEY1

                Goto continue1
            End If
        End If
continue1:
    Next
End Sub

'---------------------------------------
' 分割提取阶段处理 -- 从原始字符串中获取字符
'---------------------------------------
' 原始字符串中是否还存在未处理的字符串
Private Function hasChar() As Boolean
    hasChar = (originStrActiveIndex <= originStrLen)
End Function

' 移动到下一个活动字符
Private Sub toNextActiveChar()
    ' 截取活动索引上的字符
    activeChar = Mid(originStr, originStrActiveIndex, 1)

    ' 向下推进索引
    originStrActiveIndex = originStrActiveIndex + 1
End Sub

'------------------------------------------------------------------------------
' 分割提取阶段处理 -- 在与指定字符串有关系的字符集合中搜索是否存在更长的匹配字符串
'------------------------------------------------------------------------------
' 在 startwit + endwith 关系中搜索是否存在更长的匹配字符串
Private Function matchInRelationStrMap(ByRef targetStr As IStringNextTable) As IStringNextTable
    ' 1. 检查参数
    ' 1.1 如果原始字符串中没有剩余的字符串了，则停止匹配
    if Not hasChar Then
        ' return null
        Exit Function
    End if

    ' 2. 检查 endwith 关系
    ' 2.1 如果没有 endwith 缓存(即所有的分隔符、包围字符串对key1 之间不存在 endwith 关系)
    If Not compareStrEndwithMap Is Nothing Then
        ' 2.2 如果 compareStrEndwithMap 缓存中没有当前字符串对应的 endwith 关系，则停止匹配
        If compareStrEndwithMap.Exists(targetStr.OriginStr) Then
            Dim result As IStringNextTable
            Set result = matchInEndwithMap(targetStr)
            If Not result Is Nothing Then
                Set matchInRelationStrMap = result
                Exit Function
            End If
        End If
    End If

    ' 3. 检查 endwith 关系
    ' 3.1 如果没有 endwith 缓存(即所有的分隔符、包围字符串对key1 之间不存在 endwith 关系)
    If Not compareStrStartwithMap Is Nothing Then
        ' 3.2 如果 compareStrStartwithMap 缓存中没有当前字符串对应的 startwith 关系，则停止匹配
        If compareStrStartwithMap.Exists(targetStr.OriginStr) Then
            Set matchInRelationStrMap = matchInStartwithMap(targetStr)
        End If
        'Else
        '    'return null
        'End If
    End If
End Function

Private Function matchInEndwithMap(ByRef targetStr As IStringNextTable) As IStringNextTable
    ' 1. 获取初始遍历对象 compareStrEndwithMap
    Dim endwithArray As IObjArrayRef
    Set endwithArray = compareStrEndwithMap.Item(targetStr.OriginStr)

    ' 2. 因为 endwith 关系中的字符串参与了前面的匹配的，只是当前这个字符没有参与匹配
    '    并且因为是 endwith 关系，所以当前这个字符是最后一个
    '    如果匹配成功，则完成匹配，需要缓存这个 str
    '    如果匹配失败，则完全失败，则将这个重置这个字符串
    '
    '    因为有可能会存在多个复合规则的字符串，但是这里只保存长度最长的那个字符串
    '    因为数组中的字符串是按照长度生序排列的，所以只需要保留最后一个匹配成功的字符串即可
    Dim lastMatchedStr As IStringNextTable
    Dim i As Long
    Dim elem As IStringNextTable
    With endwithArray
        For i = 0 To .MaxIndex
            Set elem = .Item(i)

            elem.matchChar activeChar
            If elem.isWholeMatched Then
                If Not lastMatchedStr Is Nothing Then
                    ' 对于已经完全匹配，但是不是最长的字符串来说，也要清空之前的匹配结果
                    lastMatchedStr.reset
                End If

                Set lastMatchedStr = elem
            Else
                elem.reset
            End If
        Next
    End With

    If lastMatchedStr Is Nothing Then
        ' 如果没有找到匹配字符串，则返回空
        ' return null
        Exit Function
    Else
        ' 如果发现了匹配字符串，则需要迭代检查
        Dim tempMatchedStr As IStringNextTable
        Set tempMatchedStr = matchInRelationStrMap(lastMatchedStr)
        If tempMatchedStr Is Nothing Then
            Set matchInEndwithMap = lastMatchedStr
        Else
            Set matchInEndwithMap = tempMatchedStr
        End If
    End If
End Function

' 在以 targetStr 开头的字符串中继续搜索匹配的字符串
' @return 如果没有找到符合规则的字符串，将会返回 Nothing
Private Function matchInStartwithMap(ByRef targetStr As IStringNextTable) As IStringNextTable
    ' 1. 初始化遍历内容
    ' 1.1 开始时，将 targetStr 在 compareStrStartwithMap 缓存中的数组作为遍历对象
    Dim startwithArray As IObjArrayRef
    Set startwithArray = compareStrStartwithMap.Item(targetStr.OriginStr)

    ' 1.2 再次过滤
    '      用来防止 ABC(忽略大小写) + abcd 对 AbCd 的识别
    '      如果不主动检查前缀部分，则将会变成: 将 AbCd 识别成了 abcd
    ' 从原始字符串中截取 targetStr 长度的字符串
    ' startwithArray 中的每一个字符串和截取到的字符串做 startwith 检查
    ' 如果 startwith 检查失败，则忽略那个字符串
    ' 如果符合 startwith 规则，则将那个字符串的活动索引设置为 targetStr.Length
    ' 如果 startwithArray 中的所有字符串都被忽略了，则返回空对象
    Dim originMatchedStr As String
    originMatchedStr = Mid(originStr, originStrActiveIndex - targetStr.Length, targetStr.Length)

    Dim i As Long
    Dim elem As IStringNextTable
    Dim nextLoopArray As IObjArrayRef
    Set nextLoopArray = UtilsArrayRef.createRealObjArrayRef(startwithArray.Count)

    For i = 0 To startwithArray.MaxIndex
        Set elem = startwithArray.Item(i)
        If elem.startwithStr(originMatchedStr) Then
            elem.setActiveIndex targetStr.Length
            nextLoopArray.Add elem
        End If
    Next

    If nextLoopArray.Count = 0 Then
        Set matchInStartwithMap = Nothing
        Exit Function
    End If
    Set startwithArray = nextLoopArray

    ' 1.3 用来保存下一次遍历时的目标字符串
    Dim tempLoopArray As IObjArrayRef
    Set nextLoopArray = UtilsArrayRef.createRealObjArrayRef(startwithArray.Count)

    ' 1.4 因为 targetStr 的 startwith 集合的匹配存在失败的情况，所以在这里暂存原始字符串的活动索引
    '     以便失败时回退索引
    Dim originStrActiveIndexBackup As Long
    originStrActiveIndexBackup = originStrActiveIndex

    ' 1.5 保存最终匹配成功的字符串
    Dim matchedStr As IStringNextTable

    ' 2. 开始遍历、匹配
    while hasChar
        ' 2.1 移动到下一个活动字符
        toNextActiveChar

        ' 2.2 遍历所有以 targetStr 开头的字符串，并匹配
        For i = 0 to startwithArray.MaxIndex
            Set elem = startwithArray.Item(i)

            ' 比较字符字符，如果失败，则忽略这个 elem
            If Not elem.equalActiveChar(activeChar) Then
                Goto continue1
            End If

            ' 向下推进匹配字符串的索引
            elem.toNextActiveIndex

            ' 如果发现了一个已经匹配完成的字符串，则立刻结束遍历
            If elem.isWholeMatched Then
                Set matchedStr = elem
                Goto whileEnd
            End If

            ' 缓存这个字符串
            nextLoopArray.Add elem
continue1:
        Next

        ' 2.3 如果这一圈遍历中，没有任何收集到任何可用的字符串，则退出循环
        If nextLoopArray.Count = 0 Then
            Goto whileEnd
        End If

        ' 2.4 交换遍历的缓存，以进行下一次遍历
        Set tempLoopArray = startwithArray
        Set startwithArray = nextLoopArray
        Set nextLoopArray = tempLoopArray

        nextLoopArray.clear
    wend
whileEnd:

    If matchedStr is nothing Then
        ' 3. 如果没有找到匹配字符串，则需要重置活动索引，然后返回 nothing
        originStrActiveIndex = originStrActiveIndexBackup
        Set matchInStartwithMap = Nothing
    Else
        ' 5. 如果发现了匹配字符串，则需要迭代检查
        Dim tempMatchedStr As IStringNextTable
        Set tempMatchedStr = matchInRelationStrMap(matchedStr)

        ' 如果没有新的结果，则说明当前这个字符串就是最终的结果，则返回 matchedStr
        ' 如果有新的结果，则说明新得到的这个字符串才是最终结果，返回 tempMatchedStr
        If tempMatchedStr Is Nothing Then
            Set matchInStartwithMap = matchedStr
        Else
            Set matchInStartwithMap = tempMatchedStr
        End If
    End if
End Function

'------------------------------------------------------------------------------
' 分割提取阶段处理 -- 分割字符串
'------------------------------------------------------------------------------
' 对原始字符串进行分割，并抽取可用的字串
' @param targetStr 分割字符串
' @return 是否存在可用的字串
Private Function splitAndExtractFromOriginStr(ByRef targetStr As IStringNextTable) As Boolean
    Dim result As Boolean

    ' 1. 计算当前搜索到的目标字符串的起始位置
    Dim currentTargetStrStartIndex As Long
    '    在这里 originStrActiveIndex 已经定位到 matchedStr 的后面一位了
    currentTargetStrStartIndex = originStrActiveIndex - targetStr.Length

    ' 2. 获取参数的分割类型
    Dim strType As Integer
    strType = compareStrTypeMap.Item(targetStr.OriginStr)

    If strType = IGNORABLE_DELIMITER Then
        ' 处理可忽略分隔符
        If nextTokenStartIndex = currentTargetStrStartIndex Then
            ' 忽略当前的目标字符串
            ' 当前没有抽取到任何字符串，返回 False
            result = False
        Else
            ' 忽略当前的目标字符串
            ' 截取前面的有效字符串并缓存
            nextToken = Mid(originStr, nextTokenStartIndex, currentTargetStrStartIndex - nextTokenStartIndex)
            result = True
        End If

    ElseIf strType = RETAINABLE_DELIMITER Then
        ' 处理不可忽略分隔符

        If nextTokenStartIndex = currentTargetStrStartIndex Then
            ' 截取并缓存当前的目标字符串
            nextToken = Mid(originStr, nextTokenStartIndex, targetStr.Length)

            result = True
        Else
            ' 截取前面的有效字符串并缓存
            nextToken = Mid(originStr, nextTokenStartIndex, currentTargetStrStartIndex - nextTokenStartIndex)

            ' 截取并缓存当前的目标字符串
            noConsumeStr = Mid(originStr, currentTargetStrStartIndex, targetStr.Length)

            result = True
        End If

    Else
        ' 处理包围字符串对的 key1

        If nextTokenStartIndex = currentTargetStrStartIndex Then
            ' 进入包围搜索模式，缓存搜索到的包围字符串 = key1 + 搜索结果
            matchSurroundMode targetStr
            nextToken = Mid(originStr, currentTargetStrStartIndex, originStrActiveIndex - currentTargetStrStartIndex)

            result = True
        Else
            ' 截取前面的有效字符串并缓存
            nextToken = Mid(originStr, nextTokenStartIndex, currentTargetStrStartIndex - nextTokenStartIndex)

            ' 进入包围搜索模式，缓存搜索到的包围字符串 = key1 + 搜索结果
            matchSurroundMode targetStr
            noConsumeStr = Mid(originStr, currentTargetStrStartIndex, originStrActiveIndex - currentTargetStrStartIndex)

            result = True
        End If
    End If

    ' 保存下一次分割字符串的起始索引
    nextTokenStartIndex = originStrActiveIndex

    splitAndExtractFromOriginStr = result
End Function

' 启动包围搜索
' 在该方法内只负责搜索，不需要生成任何字符串
'
' 发现了一个 key1 后，开始执行搜索，在这里不需要生成任何结果，只需要遍历
'   1. 如果发现以某个 key2 结束，则此时活动索引会定位到 key2 结束索引的下一位
'   2. 如果直到原始字符串的最后也没有发现任何一个 key2，则从 key1 到原始字符串的结束全部都是包围字符串
'
' 所以无论哪种情况，都会将原始字符串的活动索引定位到一个新的结果
Private Sub matchSurroundMode(ByRef k1 As IStringNextTable)
    ' 1. 如果原始字符串中没有剩余的字符串了，则停止匹配
    if Not hasChar Then
        Exit Sub
    End if

    ' 2. 获取当前 key1 对应的所有 key2
    Dim key2Array As IObjArrayRef
    ' surroundPairMap<String, ArrayRef<IStringNextTable>>
    Set key2Array = surroundPairMap.Item(k1.OriginStr)

    ' 3. 遍历 key2Array，将所有前缀表的活动索引设置为 0
    Dim i As Long
    For i = 0 To key2Array.MaxIndex
        key2Array.Item(i).reset
    Next

    ' 4. 遍历原始字符串，尝试搜索符合规则的 key2
    Dim matchedStr As IStringNextTable
    Dim elem As IStringNextTable
    Dim tempStr As IStringNextTable
    while hasChar
        toNextActiveChar

        ' 遍历所有 key2 并进行匹配
        Set matchedStr = Nothing
        For i = 0 To key2Array.MaxIndex
            Set elem = key2Array.Item(i)

            ' 匹配当前字符，如果匹配成功，则前缀表的索引向下移动，否则向前回溯
            elem.matchChar activeChar

            ' 如果发现某个数据已经匹配成功了，则立刻结束
            if elem.isWholeMatched Then
                Set matchedStr = elem
                exit for
            end if
        next

        If Not matchedStr Is Nothing Then
            ' 如果发现了一个匹配成功的字符串
            ' 以 matchedStr 为准，在 key2StartWithMap 缓存中继续搜素
            matchInSurroundKey2StartWithMap k1, matchedStr

            ' 无论 matchedStr 的搜索结果如何，直接结束，因为到此一定找到了匹配对象
            ' 可能是 matchedStr，或是以 matchedStr 开头的某个字符串，但这里不需要关系是哪个
            Exit Sub
        End if
    Wend

    ' 进入这里说明原始字符串在匹配到 key1 之后，剩余的所有字符串中不包含任何一个 key2
    ' 所以 key1 之后的所有字符串都是包围字符串
End Sub

' 对找到的一个 key2 进行 startwith 搜索
' @param key1 key2 所属的 key1
' @param key2
Private Sub matchInSurroundKey2StartWithMap(ByRef k1 As IStringNextTable, ByRef k2 As IStringNextTable)
    ' 1. 检查参数
    ' 1.1 如果原始字符串中没有剩余的字符串了，则停止匹配
    if Not hasChar Then
        Exit Sub
    End if

    ' 1.2 如果缓存不存在，则立刻停止（说明所有 key1 下的 key2 间都不存在 startwith 这种关系）
    If surroundKey2StartWithMap Is Nothing Then
        Exit Sub
    End If

    ' 1.3 如果缓存中不存在 key1 或 key2 则立刻停止
    If Not surroundKey2StartWithMap.Exists(k1.OriginStr) Then
        Exit Sub
    End If

    Dim key1Map As Object
    Set key1Map = surroundKey2StartWithMap.Item(k1.OriginStr)

    If Not key1Map.Exists(k2.OriginStr) Then
        Exit Sub
    End If

    ' 2. 初始化遍历内容
    ' 2.1 开始时，将 key2StartWithMap 缓存中的数组作为遍历对象
    Dim startwithArray As IObjArrayRef
    Set startwithArray = key1Map.Item(k2.OriginStr)

    ' 2.2 再次过滤
    '      用来防止 ABC(忽略大小写) + abcd 对 AbCd 的识别
    '      如果不主动检查前缀部分，则将会变成: 将 AbCd 识别成了 abcd
    ' 从原始字符串中截取 k2 长度的字符串
    ' startwithArray 中的每一个字符串和截取到的字符串做 k2 检查
    ' 如果 startwith 检查失败，则忽略那个字符串
    ' 如果符合 startwith 规则，则将那个字符串的活动索引设置为 k2.Length
    ' 如果 startwithArray 中的所有字符串都被忽略了，则返回空对象
    Dim originMatchedStr As String
    originMatchedStr = Mid(originStr, originStrActiveIndex - k2.Length, k2.Length)

    Dim i As Long
    Dim elem As IStringNextTable
    Dim nextLoopArray As IObjArrayRef
    Set nextLoopArray = UtilsArrayRef.createRealObjArrayRef(startwithArray.Count)

    For i = 0 To startwithArray.MaxIndex
        Set elem = startwithArray.Item(i)
        If elem.startwithStr(originMatchedStr) Then
            elem.setActiveIndex k2.Length
            nextLoopArray.Add elem
        End If
    Next

    If nextLoopArray.Count = 0 Then
        Exit Sub
    End If
    Set startwithArray = nextLoopArray

    ' 2.3 用来保存下一次遍历时的目标字符串
    Dim tempLoopArray As IObjArrayRef
    Set nextLoopArray = UtilsArrayRef.createRealObjArrayRef(startwithArray.Count)

    ' 2.4 因为 k2 的 startwith 集合的匹配存在失败的情况，所以在这里暂存原始字符串的活动索引
    '     以便失败时回退索引
    Dim originStrActiveIndexBackup As Long
    originStrActiveIndexBackup = originStrActiveIndex

    ' 2.5 保存最终匹配成功的字符串
    Dim matchedStr As IStringNextTable

    ' 3. 开始遍历、匹配
    while hasChar
        ' 3.1 移动到下一个活动字符
        toNextActiveChar

        ' 3.2 遍历所有以 key2 开头的字符串，并匹配
        For i = 0 to startwithArray.MaxIndex
            Set elem = startwithArray.Item(i)

            ' 比较字符字符，如果失败，则忽略这个 elem
            If Not elem.equalActiveChar(activeChar) Then
                Goto continue1
            End If

            ' 向下推进匹配字符串的索引
            elem.toNextActiveIndex

            ' 如果发现了一个已经匹配完成的字符串，则立刻结束遍历
            If elem.isWholeMatched Then
                Set matchedStr = elem
                Goto whileEnd
            End If

            ' 缓存这个字符串
            nextLoopArray.Add elem
continue1:
        Next

        ' 3.3 如果这一圈遍历中，没有任何收集到任何可用的字符串，则退出循环
        If nextLoopArray.Count = 0 Then
            Goto whileEnd
        End If

        ' 3.4 交换遍历的缓存，以进行下一次遍历
        Set tempLoopArray = startwithArray
        Set startwithArray = nextLoopArray
        Set nextLoopArray = tempLoopArray

        nextLoopArray.clear
    wend
whileEnd:

    If matchedStr is nothing Then
        ' 如果没有找到匹配字符串，则需要重置活动索引，然后返回 nothing
        originStrActiveIndex = originStrActiveIndexBackup
    Else
        ' 6. 如果发现了匹配字符串，则需要迭代当前方法
        '    不需要关系具体结果
        '       如果再下一层的匹配成功了，原始字符串的活动索引将会被定位到新的位置
        '       如果再下一层匹配失败，下一层会主动回退活动索引
        matchInSurroundKey2StartWithMap k1, matchedStr
    End if

End Sub

'------------------------------------------------------------------------------
' 收尾处理
'------------------------------------------------------------------------------
' 遍历 compareStrArray 中的所有字符串，将每个字符串的活动索引设置为 0
Private Sub resetCompareStrArray()
    Dim i As Long
    For i = 0 To compareStrArray.MaxIndex
        compareStrArray.Item(i).reset
    Next
End Sub
